<script setup>
import { ElMessage, ElMessageBox, genFileId, ElNotification } from "element-plus"
import { reactive, ref, onBeforeUnmount, nextTick } from "vue"
import { base_upload_url, base_download, BASE_URL } from '../../config.js'
import { DeleteFileService } from "@/api/file.js"
import { YoloService } from "@/api/yolo.js"
import io from 'socket.io-client'
import { More } from '@element-plus/icons-vue'

const header = {
  'Authorization': localStorage.getItem('Authorization')
}

// 提交检测时的发送表单
const form = reactive({
  type: 'image',
  image: '',
  video: '',
  image_size: 640,
  // 置信度阈值
  conf_threshold: 0.25,
  // 交并比阈值
  iou_threshold: 0.7,
  // 批次大小
  batch: 1,
  // 最大目标检测数
  max_det: 300,
  // 帧率采样步长
  vid_stride: 1,
  // 数据增强
  augment: false,
  // 非极大值抑制
  agnostic_nms: false,
  //  目标类别
  classes: [0, 1, 2],
  // 高分辨率分割掩膜
  retina_masks: false

})

const upload = ref()
// 结果检测状态，0为未提交，1为正在检测，2为有结果
const result_status = ref(0)

// upload_url = BASE_URL + '/file/upload_image'
// const upload_url = base_upload_url + form.image
// 从服务器下载刚上传的文件 基本地址
const base_download_upload_url = ref(base_download + 'upload/')
// 服务器处理结果的基本地址 需拼接文件名
const base_download_result_url = ref(base_download + 'result/')

// 从服务器下载刚上传的文件 地址
const download_upload_url = ref('')
// 从服务器下载结果文件 地址
const download_result_url = ref('')

// 初始支持的文件类型
const fileTypeList = ref(["png", "jpg", "jpeg", "webp", 'bmp', 'dng', 'mpo', 'tif', 'tiff', 'pfm', 'HEIC'])
const upload_accept = ref(".png, .jpg, .jpeg, .webp, .bmp, .dng, .mpo, .tif, .tiff', .pfm, .HEIC")

// 改变检测类型执行的函数
const change_type = async (type) => {
  // console.log(form.type)
  if (type == 'image') {
    fileTypeList.value = ["png", "jpg", "jpeg", "webp", 'bmp', 'dng', 'mpo', 'tif', 'tiff', 'pfm', 'HEIC']
    upload_accept.value = ".png, .jpg, .jpeg, .webp, .bmp, .dng, .mpo, .tif, .tiff', .pfm, .HEIC"
  } else if (type == 'video') {
    fileTypeList.value = ["mp4", "avi", "gif", "m4v", "asf", "mkv", "mov", "mpeg", "mpg", "ts", "wmv", "webm"]
    upload_accept.value = ".mp4, .avi, .gif, .m4v, .asf, .mkv, .mov, .mpeg, .mpg, .ts, .wmv, .webm,"
  }
  // 如果摄像头是打开的，则需要关闭摄像头
  if (camera_open) {
    stopCamera()
  }

}

// 上传文件之前对文件格式检测
const beforeUpload = (file) => {
  // const fileTypeList = ["png", "jpg", "jpeg", "webp"]
  // console.log('run')
  // console.log(fileTypeList)
  const fileType = file.name.substring(file.name.lastIndexOf(".") + 1)
  if (fileTypeList.value.indexOf(fileType) == -1) {
    ElMessage.error("文件格式错误")
    return false
  }
}

// 文件清除函数
const handleRemove = async () => {
  // console.log('delete is running')
  if (form.type == 'image') {
    // await DeleteFileService(form.image)
    form.image = ''
  } else {
    // await DeleteFileService(form.video)
    form.video = ''
  }
  upload.value.clearFiles()
  download_upload_url.value = ''
}

// 文件超出限制时执行
const handleExceed = (files) => {
  upload.value.clearFiles()
  const file = files[0]
  upload.value.handleStart(file)
  upload.value.submit()

}

// 文件上传成功回调函数
const handleSuccess = (res) => {
  result_status.value = 0
  if (form.type == 'image') {
    form.image = res.data
    form.video = ''
    // 拼接上传文件在服务器中的下载地址
    download_upload_url.value = base_download_upload_url.value + form.image
  } else {
    form.video = res.data
    form.image = ''
    download_upload_url.value = base_download_upload_url.value + form.video
  }
  // console.log(upload_image_url)
  // console.log(download_upload_url.value)
  ElMessage.success("上传成功")
}

// 更多配置弹窗标记
const dialogMoreVisible = ref(false)
// 更多配置弹窗的临时表单存储
const dialogMoreForm = reactive({
  iou_threshold: 0.7,
  batch: 1,
  max_det: 300,
  vid_stride: 1,
  augment: false,
  agnostic_nms: false,
  classes: [0, 1, 2],
  retina_masks: false
})
// 更多配置弹窗打开回调
// const dialogMoreOpen = () => {

// }
// 重置更多配置弹窗临时表单内容
const resetDialogMoreForm = () => {
  dialogMoreForm.iou_threshold = form.iou_threshold
  dialogMoreForm.batch = form.batch
  dialogMoreForm.max_det = form.max_det
  dialogMoreForm.vid_stride = form.vid_stride
  dialogMoreForm.augment = form.augment
  dialogMoreForm.agnostic_nms = form.agnostic_nms
  dialogMoreForm.classes = form.classes
  dialogMoreForm.retina_masks = form.retina_masks
}
// 取消修改配置函数
const dialogMoreCancel = () => {
  ElMessageBox.confirm(
    '确认要取消已经修改的配置吗?',
    'Warning',
    {
      confirmButtonText: '确认',
      cancelButtonText: '取消',
      type: 'warning',
    }
  )
    .then(() => {
      resetDialogMoreForm()
      dialogMoreVisible.value = false
    })
    .catch(() => {
      return
    })
}
// 确认更多配置修改函数
const dialogMoreConfirm = () => {
  // console.log(dialogMoreForm)
  form.iou_threshold = dialogMoreForm.iou_threshold
  form.batch = dialogMoreForm.batch
  form.max_det = dialogMoreForm.max_det
  form.vid_stride = dialogMoreForm.vid_stride
  form.augment = dialogMoreForm.augment
  form.agnostic_nms = dialogMoreForm.agnostic_nms
  form.classes = dialogMoreForm.classes
  form.retina_masks = dialogMoreForm.retina_masks
  dialogMoreVisible.value = false
  // ElNotification({
  //   title: '配置项修改成功',
  //   message: `交并比阈值:${form.iou_threshold}批次大小:${form.batch}
  //   最大目标检测数:${form.max_det}帧率采样步长:${form.vid_stride}
  //   数据增强:${form.augment ? "开" : "关"}非极大值抑制:${form.agnostic_nms ? "开" : "关"}
  //   高分辨率分割掩膜:${form.retina_masks ? "开" : "关"}目标类别:${form.classes}`,
  // })
  // console.log(form)
}

const detectResultData = reactive({
  'result': null,
  'speed': {
    'inference': 0.0,
    'postprocess': 0.0,
    'preprocess': 0.0
  }
})

// 提交检测
const sumbitDetect = async () => {
  result_status.value = 1
  let res
  // console.log(form)
  try {
    res = await YoloService(form)
  }
  catch {
    result_status.value = 0
    return
  }
  result_status.value = 2
  // 拼接结果地址
  console.log(res.data)
  detectResultData.result = res.data.result
  detectResultData.speed = res.data.speed
  console.log(detectResultData.result)
  download_result_url.value = base_download_result_url.value + res.data.save_dir
  ElMessage.success("检测成功")
}

// 重置参数
const reset = () => {
  form.image_size = 640
  form.conf_threshold = 0.25
  form.iou_threshold = 0.7,
    form.batch = 1,
    form.max_det = 300,
    form.vid_stride = 1,
    form.augment = false,
    form.agnostic_nms = false,
    form.classes = [0, 1, 2],
    form.retina_masks = false
  resetDialogMoreForm()

}


// ---摄像头实时检测

// 摄像头拍摄结果video DOM对象
const videoEl = ref(null)
// canvas DOM对象
const canvasEl = ref(null)
// 结果框内展示的图片地址
const processedImageSrc = ref('')
// 摄像头是否打开的标记
const camera_open = ref(false)

const mediaStreamTrack = ref(null)

// 状态变量
// const socket = io('http://localhost:12138')
var socket = null
let animationId = null
let frameCount = 0
const captureInterval = 40
const cameraFacingMode = ref('user')

// 初始化摄像头
const initCamera = async () => {
  await nextTick()
  if (videoEl.value) {
    try {
      // const stream = await navigator.mediaDevices.getUserMedia({ video: true })
      // videoEl.value.srcObject = stream;
      // await videoEl.value.play();
      navigator.mediaDevices
        // 开启视频，关闭音频
        .getUserMedia({ audio: false, video: { facingMode: cameraFacingMode.value } })
        .then((stream) => {
          // 将视频流传入viedo控件
          videoEl.value.srcObject = stream
          // console.log(videoEl.value)
          mediaStreamTrack.value = stream
          // 播放
          videoEl.value.play()
        })
        .catch((err) => {
          console.log(err);
        });
      await startCapturing()
    } catch (error) {
      console.error("摄像头初始化失败：", error)
      ElMessage.error("摄像头初始化失败")
    }
  } else {
    console.error("video 元素未正确挂载")
  }
}

// 添加一个切换摄像头的函数
async function switchCamera() {
  // 切换摄像头朝向
  cameraFacingMode.value = cameraFacingMode.value === 'user' ? 'environment' : 'user';
  // 停止当前的媒体流
  stopCamera()
  // 重新获取新的媒体流
  camera_detect()
}

// 停止摄像头的函数
const stopCamera = () => {
  if (animationId) {
    cancelAnimationFrame(animationId);
    animationId = null;
  }
  if (videoEl.value && videoEl.value.srcObject) {
    videoEl.value.srcObject.getTracks().forEach((track) => track.stop());
    videoEl.value.srcObject = null;
  }
  if (processedImageSrc.value) {
    URL.revokeObjectURL(processedImageSrc.value);
    processedImageSrc.value = "";
  }
  camera_open.value = false
  socket.disconnect()
  result_status.value = 0
  videoEl.value = null
  canvasEl.value = null
  socket = null
};

// 开始捕获帧
const startCapturing = async () => {
  // await nextTick()
  // console.log(canvasEl.value)
  result_status.value = 2
  const canvas = canvasEl.value
  const ctx = canvas.getContext('2d')

  const captureFrame = () => {
    ctx.drawImage(videoEl.value, 0, 0, canvas.width, canvas.height)

    canvas.toBlob(blob => {
      frameCount++
      if (!blob) return
      // if (frameCount % captureInterval === 0) {
      //   // 发送帧到服务器
      //   const reader = new FileReader()
      //   reader.onloadend = () =>  socket.emit('video_frame', reader.result)
      //   reader.readAsArrayBuffer(blob)
      // } else {
      //   // 本地显示原始帧
      //   updateProcessedImage(blob)
      // }
      const reader = new FileReader()
      reader.onloadend = () => {
        // if (socket && socket.connected) {
        // console.log(reader.result)
        socket.emit('video_frame', {
          data: reader.result,
          flag: frameCount % captureInterval === 0
        })
        // }
      }
      reader.readAsArrayBuffer(blob)
    }, 'image/webp')

    animationId = requestAnimationFrame(captureFrame)
  }

  animationId = requestAnimationFrame(captureFrame)
}

// 更新处理后的图片
// const updateProcessedImage = (blob) => {
//   const newUrl = URL.createObjectURL(blob);
//   const oldUrl = processedImageSrc.value;
//   processedImageSrc.value = newUrl;
//   if (oldUrl) {
//     URL.revokeObjectURL(oldUrl); // 释放旧 URL
//   }
// }

// 生命周期钩子
const camera_detect = () => {
  // form.type = 'camera'
  // console.log(form.type)
  camera_open.value = true
  initCamera()
  if (!socket) {
    socket = io(BASE_URL)
  }
  socket.on('connect', () => {
    console.log('Socket 连接成功');
    ElMessage.success('Socket 连接成功')
    // 移除旧的监听
    // socket.off('processed-image');
    // let currentBlobUrl = null;
    socket.on('processed-image', (event) => {
      // console.log('running');
      // console.log(event.image instanceof ArrayBuffer);
      if (event.image instanceof ArrayBuffer) {
        let data = event.image;
        // console.log(event)
        let blob = new Blob([data], { type: 'image/webp' });
        let url = URL.createObjectURL(blob);
        processedImageSrc.value = url
      } else {
        processedImageSrc.value = event.image;
      }

    });
    socket.on('disconnect', (event) => {
      console.log('Socket disconnected:', event)
    })

    socket.on('error', (error) => {
      console.log('Socket error:', error)
    })

  });

  // initCamera()

  // // socket.off('processed-image')
  // // Socket.IO事件监听
  // socket.on('processed-image', (event) => {
  //   // const newUrl = event.image;
  //   // const oldUrl = processedImageSrc.value;
  //   // processedImageSrc.value = newUrl;
  //   // if (oldUrl) {
  //   //   URL.revokeObjectURL(oldUrl); // 释放旧 URL
  //   // }
  //   console.log('running')
  //   console.log(event)
  //   processedImageSrc.value = event.image
  // })

  // socket.on('disconnect', (event) => {
  //   console.log('Socket disconnected:', event)
  // })

  // socket.on('error', (error) => {
  //   console.error('Socket error:', error)
  // })
}

// 确认打开摄像头
const open_camera_detect = () => {
  ElMessageBox.confirm(
    '是否确认开启摄像头检测，可能会消耗较多硬件资源造成计算机和服务器卡顿？且此模式无法自定义检测参数。',
    '开启摄像头',
    {
      confirmButtonText: '确认',
      cancelButtonText: '取消',
      type: 'warning',
    }
  )
    .then(() => {
      // ElMessage({
      //   type: 'success',
      //   message: 'Delete completed',
      // })
      form.image = ''
      form.video = ''
      camera_detect()
    })
    .catch(() => {
      // ElMessage({
      //   type: 'info',
      //   message: 'Delete canceled',
      // })
      form.type = 'image'
      change_type("image")
    })
}

onBeforeUnmount(() => {
  // 清理资源
  try {
    if (animationId) cancelAnimationFrame(animationId)
    if (videoEl.value.srcObject) {
      videoEl.value.srcObject.getTracks().forEach(track => track.stop())
    }
    socket.disconnect()
    if (processedImageSrc.value) {
      URL.revokeObjectURL(processedImageSrc.value);
    }
  }
  catch (e) {
    console.log("清理资源失败")
  }
})

</script>

<template>
  <div class="common-layout">
    <div class="left">
      <el-upload ref="upload" class="upload-demo" :accept=upload_accept :action='base_upload_url'
        :on-remove="handleRemove" :before-upload="beforeUpload" :limit="1" :on-exceed="handleExceed" drag="True"
        :on-success="handleSuccess" :headers="header">
        <el-image v-if="form.image" :src="download_upload_url" class="upload_image" />
        <video v-else-if="form.video" class="upload_video" controls loop="False" :key="download_upload_url">
          <source :src="download_upload_url" />
        </video>
        <template v-else-if="camera_open">
          <video ref="videoEl" class="upload_video"></video>
          <canvas ref="canvasEl" style="display: none" width="276" height="200"></canvas>
        </template>
        <el-icon v-else class="uploader-icon">
          <upload-filled />
        </el-icon>
        <div class="el-upload__text">
          拖拽或<em>点击上传</em>
        </div>
      </el-upload>
      <el-form :model="form" label-position="top" label-width="auto" class="el-from">
        <el-form-item label="检测类型">
          <el-radio-group fill="#06669d" v-model="form.type">
            <el-radio-button label="图片" value="image" @click="change_type('image')" />
            <el-radio-button label="视频" value="video" @click="change_type('video')" />
            <el-radio-button label="摄像头" value="camera" @click="open_camera_detect" />
          </el-radio-group>
          <el-button v-if="camera_open" @click="switchCamera" class="change_camera_button">
            切换摄像头
          </el-button>
          <div class="more">
            <el-popover placement="top" width="auto" trigger="hover" content="修改更多检测配置项">
              <template #reference>
                <el-link :disabled="camera_open" @click="dialogMoreVisible = true">
                  更多配置
                </el-link>
              </template>
            </el-popover>
          </div>
          <!-- <el-icon><More /></el-icon> -->
        </el-form-item>
        <el-form-item label="选择尺寸大小">
          <el-slider v-model="form.image_size" show-input :min='320' :max='1280' class="form-slider" />
        </el-form-item>
        <el-form-item label="置信度阈值">
          <el-slider v-model="form.conf_threshold" show-input :min='0' :max='1' class="form-slider" step="0.01" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="sumbitDetect" :loading="result_status == 1"
            :disabled="camera_open || (!form.image && !form.video)">提交检测</el-button>
          <el-button type="info" @click="reset">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="right">
      <!-- <el-image v-if="form.image" :src="upload_image_url" class="result_image" /> -->
      <div class="result-demo" v-loading="result_status == 1" element-loading-text="检测中，请稍等...">
        <div class="image-slot" v-if="result_status < 1">
          <el-empty description="无检测结果" />
        </div>
        <div v-if="result_status == 2">
          <el-image v-if="form.image" :src="download_result_url" class="result_show" />
          <video v-else-if="form.video" class="result_show" autoplay controls loop="True" :key="download_result_url">
            <source :src="download_result_url" />
          </video>
          <el-image v-else-if="camera_open" :src="processedImageSrc" class="result_show" />
        </div>
      </div>
      <div v-if="result_status == 2" class="right_result">
        <!-- <p>输入数据预处理耗时: {{ detectResultData.speed.preprocess }}ms<br>实际执行神经网络计算用时: {{ deleteResultData.speed.inference
          }}ms<br>
          预测结果后处理耗时: {{ detectResultData.speed.postprocess }}ms</p> -->
        <el-row>
          <el-col :span="6">
            <el-statistic title="数据预处理耗时" :value="detectResultData.speed.preprocess || 'error'" :precision="4" />
          </el-col>
          <el-col :span="6">
            <el-statistic title="神经网络计算用时" :value="detectResultData.speed.inference || 'error'" :precision="4" />
          </el-col>
          <el-col :span="6">
            <el-statistic title="结果后处理耗时" :value="detectResultData.speed.postprocess || 'error'" :precision="4" />
          </el-col>
          <el-col :span="6" v-if="form.type == 'image'">
            <el-statistic title="检测结果数量" :value="detectResultData.result[0].length || 'error'" />
          </el-col>
          <el-col :span="6" v-if="form.type == 'video'">
            <el-statistic title="检测帧数" :value="detectResultData.result.length || 'error'" />
          </el-col>
        </el-row>
      </div>
    </div>
  </div>

  <el-dialog v-model="dialogMoreVisible" title="更多检测配置项" width="600" :before-close="dialogMoreCancel"
    :open="resetDialogMoreForm">
    <el-form :model="dialogMoreForm" label-position="top" label-width="auto" class="dialogMoreForm">
      <el-form-item label="交并比阈值">
        <!-- <el-input v-model="form.iou_threshold" autocomplete="off" /> -->
        <el-slider v-model="dialogMoreForm.iou_threshold" show-input :min='0.01' :max='1' step="0.01"
          class="dialogMoreFormSlider" />
      </el-form-item>
      <el-form-item label="批次大小" v-if="form.type == 'video'">
        <el-slider v-model="dialogMoreForm.batch" show-input :min='1' :max='12' step="1" class="dialogMoreFormSlider" />
      </el-form-item>
      <el-form-item label="最大目标检测数">
        <el-slider v-model="dialogMoreForm.max_det" show-input :min='1' :max='300' step="1"
          class="dialogMoreFormSlider" />
      </el-form-item>
      <el-form-item label="帧率采样步长" v-if="form.type == 'video'">
        <el-slider v-model="dialogMoreForm.vid_stride" show-input :min='1' :max='60' step="1"
          class="dialogMoreFormSlider" />
      </el-form-item>
      <div style="display: flex;flex-wrap: wrap;">
        <el-form-item label="数据增强" label-position="left" class="dialogMoreFormSwitch">
          <el-switch v-model="dialogMoreForm.augment" />
        </el-form-item>
        <el-form-item label="非极大值抑制" label-position="left" class="dialogMoreFormSwitch">
          <el-switch v-model="dialogMoreForm.agnostic_nms" />
        </el-form-item>
        <el-form-item label="高分辨率分割掩膜" label-position="left" class="dialogMoreFormSwitch">
          <el-switch v-model="dialogMoreForm.retina_masks" />
        </el-form-item>
      </div>
      <el-form-item label="目标类别">
        <el-checkbox-group v-model="dialogMoreForm.classes">
          <el-checkbox :value=0 label="裂痕" border />
          <el-checkbox :value=1 label="凹陷" border />
          <el-checkbox :value=2 label="锈蚀" border />
          <!-- <el-checkbox value=3 label="" border /> -->
        </el-checkbox-group>
      </el-form-item>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="dialogMoreCancel">取消</el-button>
        <el-button type="primary" @click="dialogMoreConfirm">
          确定
        </el-button>
      </div>
    </template>
  </el-dialog>
</template>



<style>
.common-layout {
  width: 100%;
  height: calc(100% - 90px);
  padding: 45px;
  display: block;
}

.left {
  float: left;
  width: 48%;
  /* background-color: aqua; */
}

.upload-demo {
  height: 40vh;
  width: 100%;
  /* background: #984141; */
}

.el-upload-dragger {
  width: 100%;
  height: 40vh;
  border: 1px dashed black;
}

.uploader-icon {
  margin-top: 50px;
  font-size: 100px;
  color: rgb(157, 156, 156);
}

.upload_image {
  width: auto;
  height: 35vh;
  margin-top: -25px;
}

.upload_video {
  width: auto;
  height: 35vh;
  margin-top: -25px;
}

.el-form {
  margin-top: 30px;
}

.more {
  /* float: right; */
  /* right: 0; */
  /* margin-left: 35%; */
  /* display: flex;
  justify-content: flex-end; */
  margin-left: auto;
  margin-right: 30%;
}

.form-slider {
  width: 70%;
}

.right {
  float: right;
  width: 48%;
  height: 80vh;
  /* background: #000; */
}

.result-demo {
  height: 40vh;
  width: 100%;
  border: 1px dashed black;
  border-radius: 10px;
  display: flex;
  justify-content: center;
  /* 水平居中 */
  align-items: center;
  /* 垂直居中 */
}

.image-slot {
  margin-top: 25px;
}

.result_show {
  width: auto;
  height: 35vh;
}

.change_camera_button {
  margin-left: 25px;
  background: #06669d;
  color: white;
}

.dialogMoreForm {
  margin-left: 30px;
}

.dialogMoreForm {
  margin-right: 30px;
  width: 90%;
}

.dialogMoreFormSwitch {
  margin-right: 50px;
}

.dialog-footer {
  margin-right: 20px;
}

.right_result {
  margin-top: 10px;
  margin-left: 10px;
}
</style>
